
#include "native_view_photobooth_win.h"

#include "SkBitmap.h"

#include "ui_gfx/canvas_skia.h"
#include "ui_gfx/point.h"
#include "ui_gfx/rect.h"

#include "view/widget/widget.h"

#include "tab_contents.h"

namespace
{

    static BOOL CALLBACK MonitorEnumProc(HMONITOR monitor, HDC monitor_dc,
        RECT* monitor_rect, LPARAM data)
    {
        gfx::Point* point = reinterpret_cast<gfx::Point*>(data);
        if(monitor_rect->right > point->x() && monitor_rect->bottom > point->y())
        {
            point->set_x(monitor_rect->right);
            point->set_y(monitor_rect->bottom);
        }
        return TRUE;
    }

    gfx::Point GetCaptureWindowPosition()
    {
        // Since the capture window must be visible to be painted, it must be opened
        // off screen to avoid flashing. But if it is opened completely off-screen
        // (e.g. at 0xFFFFx0xFFFF) then on Windows Vista it will not paint even if it
        // _is_ visible. So we need to find the right/bottommost monitor, and
        // position it so that 1x1 pixel is on-screen on that monitor which is enough
        // to convince Vista to paint it. Don't ask why this is so - this appears to
        // be a regression over XP.
        gfx::Point point(0, 0);
        EnumDisplayMonitors(NULL, NULL, &MonitorEnumProc,
            reinterpret_cast<LPARAM>(&point));
        return gfx::Point(point.x() - 1, point.y() - 1);
    }

}

///////////////////////////////////////////////////////////////////////////////
// NativeViewPhotoboothWin, public:

// static
NativeViewPhotobooth* NativeViewPhotobooth::Create(HWND initial_view)
{
    return new NativeViewPhotoboothWin(initial_view);
}

NativeViewPhotoboothWin::NativeViewPhotoboothWin(HWND initial_hwnd)
: capture_window_(NULL),
current_hwnd_(initial_hwnd)
{
    DCHECK(IsWindow(current_hwnd_));
    CreateCaptureWindow(initial_hwnd);
}

NativeViewPhotoboothWin::~NativeViewPhotoboothWin()
{
    // Detach the attached HWND. The creator of the photo-booth is responsible
    // for destroying it.
    Replace(NULL);
    capture_window_->Close();
}

void NativeViewPhotoboothWin::Replace(HWND new_hwnd)
{
    if(IsWindow(current_hwnd_) &&
        GetParent(current_hwnd_)==capture_window_->GetNativeView())
    {
        // We need to hide the window too, so it doesn't show up in the TaskBar or
        // be parented to the desktop.
        ShowWindow(current_hwnd_, SW_HIDE);
        SetParent(current_hwnd_, NULL);
    }
    current_hwnd_ = new_hwnd;

    if(IsWindow(new_hwnd))
    {
        // Insert the TabContents into the capture window.
        SetParent(current_hwnd_, capture_window_->GetNativeView());

        // Show the window (it may not be visible). This is the only safe way of
        // doing this. ShowWindow does not work.
        SetWindowPos(current_hwnd_, NULL, 0, 0, 0, 0,
            SWP_DEFERERASE | SWP_NOACTIVATE | SWP_NOCOPYBITS |
            SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOZORDER |
            SWP_SHOWWINDOW | SWP_NOSIZE);
    }
}

void NativeViewPhotoboothWin::PaintScreenshotIntoCanvas(
    gfx::Canvas* canvas,
    const gfx::Rect& target_bounds)
{
    // Our contained window may have been re-parented. Make sure it belongs to
    // us until someone calls Replace(NULL).
    if(IsWindow(current_hwnd_) &&
        GetParent(current_hwnd_)!=capture_window_->GetNativeView())
    {
        Replace(current_hwnd_);
    }

    // We compel the contained HWND to paint now, synchronously. We do this to
    // populate the device context with valid and current data.
    RedrawWindow(current_hwnd_, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);

    // Transfer the contents of the layered capture window to the screen-shot
    // canvas' DIB.
    HDC target_dc = canvas->BeginPlatformPaint();
    HDC source_dc = GetDC(current_hwnd_);
    RECT window_rect = { 0 };
    GetWindowRect(current_hwnd_, &window_rect);
    BitBlt(target_dc, target_bounds.x(), target_bounds.y(),
        target_bounds.width(), target_bounds.height(), source_dc, 0, 0,
        SRCCOPY);
    // Windows screws up the alpha channel on all text it draws, and so we need
    // to call makeOpaque _after_ the blit to correct for this.
    skia::MakeOpaque(canvas->AsCanvasSkia(), target_bounds.x(),
        target_bounds.y(), target_bounds.width(),
        target_bounds.height());
    ReleaseDC(current_hwnd_, source_dc);
    canvas->EndPlatformPaint();
}

///////////////////////////////////////////////////////////////////////////////
// NativeViewPhotoboothWin, private:

void NativeViewPhotoboothWin::CreateCaptureWindow(HWND initial_hwnd)
{
    // Snapshotting a HWND is tricky - if the HWND is clipped (e.g. positioned
    // partially off-screen) then just blitting from the HWND' DC to the capture
    // bitmap would be incorrect, since the capture bitmap would show only the
    // visible area of the HWND.
    //
    // The approach turns out to be to create a second layered window in
    // hyperspace the to act as a "photo booth." The window is created with the
    // size of the unclipped HWND, and we attach the HWND as a child, refresh the
    // HWND' by calling |Paint| on it, and then blitting from the HWND's DC to
    // the capture bitmap. This results in the entire unclipped HWND display
    // bitmap being captured.
    //
    // The capture window must be layered so that Windows generates a backing
    // store for it, so that blitting from a child window's DC produces data. If
    // the window is not layered, because it is off-screen Windows does not
    // retain its contents and blitting results in blank data. The capture window
    // is a "basic" (1 level of alpha) layered window because that is the mode
    // that supports having child windows (variable alpha layered windows do not
    // support child HWNDs).
    //
    // This function sets up the off-screen capture window, and attaches the
    // associated HWND to it. Note that the details are important here, see below
    // for further comments.
    //
    RECT contents_rect;
    GetClientRect(initial_hwnd, &contents_rect);
    gfx::Point window_position = GetCaptureWindowPosition();
    gfx::Rect capture_bounds(window_position.x(), window_position.y(),
        contents_rect.right - contents_rect.left,
        contents_rect.bottom - contents_rect.top);
    capture_window_ = new view::Widget;
    view::Widget::InitParams params(view::Widget::InitParams::TYPE_POPUP);
    params.transparent = true;
    params.bounds = capture_bounds;
    capture_window_->Init(params);
    // If the capture window isn't visible, blitting from the TabContents'
    // HWND's DC to the capture bitmap produces blankness.
    capture_window_->Show();
    SetLayeredWindowAttributes(
        capture_window_->GetNativeView(), RGB(0xFF, 0xFF, 0xFF), 0xFF, LWA_ALPHA);

    Replace(initial_hwnd);
}